# Copyright (c) 2018 Presto Labs Pte. Ltd.
# Author: jaewon

import datetime
import functools
import gzip
import logging
import pickle

from absl import app, flags
from tornado import gen

from coin.base.datetime_util import to_datetime

from coin.strategy.mm.bitflyer_v1.pass_unhedge_model_calc import Calculator
from experimental.prophet.craps.universe import universe_btc

from coin.strategy.mm.base.bookmap import BookMap
from coin.strategy.mm.base.bitflyer_strategy_base import (BitflyerMarginPositionBalancePrinter,
                                                          BitflyerStrategyBase,
                                                          run_live)
from coin.exchange.bitflyer_v1.kr_rest.futures_product import BitflyerCurrency
from coin.strategy.mm.bitflyer_v1.simple_mm_executor import BitflyerSimpleMMExecutor
from coin.exchange.base.strategy_logger import StrategyLogProvider
from coin.proto.coin_request_pb2 import StrategyRequestProto


class PassiveUnhedgeModelStrategy(BitflyerStrategyBase):
  def __init__(self, strategy_params, dry_run=True):
    trade_product = strategy_params['trade_product']
    super().__init__(products=[trade_product],
                     currencies=strategy_params['currencies'],
                     logger=logging.getLogger('PassiveUnhedgeModelStrategy'))

    self._strategy_params = strategy_params
    self._dry_run = dry_run
    self._calc = Calculator(eval_callback=self.on_eval, **strategy_params['calculator'])

    self._trade_product = trade_product
    self._min_pos = float(strategy_params['executor']['min_pos'])
    self._max_pos = float(strategy_params['executor']['max_pos'])
    self._pos_bias = float(strategy_params['pos_bias'])

    self._bookmap = BookMap([self._trade_product], logger=self._logger)
    self._executor = None

    request = StrategyRequestProto(**self._strategy_params['logger'])
    self._strategy_logger = StrategyLogProvider(request)
    self._strategy_logger.write_request()

    pos_bias_map = {self._trade_product: (self._pos_bias, 0.)}
    self._printer = BitflyerMarginPositionBalancePrinter(BitflyerCurrency.FromStrCurrency('JPY'),
                                                         self._trade_product,
                                                         self._bookmap,
                                                         pos_bias_map=pos_bias_map,
                                                         strategy_logger=self._strategy_logger,
                                                         logger=self._logger)

  @property
  def strategy_logger(self):
    return self._strategy_logger

  def prepare(self):
    assert self.feed_subsystem is not None

    # Subscribe feed.
    self.feed_subsystem.subscribe_fastfeed(self._calc.get_feed_sub_reqs(),
                                           self._calc.book_reset_callbacks)
    self.feed_subsystem.subscribe_book(self._trade_product,
                                       functools.partial(self.on_book, self._trade_product))

    # Start balance and position printing.
    self._printer.start_print(self._ioloop, 10)

  def on_og_reset(self):
    # Cancel all unknown working orders.
    self._ioloop.add_callback(self.periodic_cancel_unknown_working_orders)

    self._printer.register_order_gateway(self.order_gateway)

    # Intialize passive executor
    self._logger.info('Intializing passive executor...')
    self._executor = BitflyerSimpleMMExecutor(self._strategy_params['executor'],
                                              self.order_gateway,
                                              self._logger)
    # TODO(jaewon):  self.order_gateway.register_fill_callback(self._on_fill)

  @gen.coroutine
  def periodic_cancel_unknown_working_orders(self):
    while True:
      if self.order_gateway.is_ready():
        self.order_gateway.cancel_unknown_working_orders()
      yield gen.sleep(30)

  def on_book(self, product, book):
    self._bookmap.set_book(product, book)

  def on_eval(self):
    if not self._executor or not self._calc.ready or not self._calc.trigger:
      return
    '''
    pos = self.order_gateway.get_position(self._trade_product) + self._pos_bias
    '''

    buy_p, sell_p = self._calc.calculate_price()
    self._executor.manage_mm_orders(sell_price=sell_p, buy_price=buy_p)


def get_param_set(target, dry_run=False, init_og_immediately=False):
  now_dt = datetime.datetime.now()
  symbol_info = universe_btc(now_dt)[0]
  assert symbol_info.nickname == 'bitflyer-fxbtcjpy'
  trade_product = symbol_info.product

  lot_size = 0.2  # BTC
  stack = 5
  pos_bias = 0.
  sampler_offsets = {
      'sampler/low_vol': 5.,
      'sampler/mid_vol': 5.,
      'sampler/high_vol': 5.,
  }
  og_config = 'bitflyer_japan1'
  strategy_name = '2.pass-unhedge-model.bitflyer-fxbtcjpy.japan1'

  with gzip.open('model_bitflyer-fxbtcjpy.pkl.gz', 'rb') as f:
    model = pickle.load(f)

  NS_PER_MINUTE = 60 * (10**9)
  strategy_params = {
      'currencies': [BitflyerCurrency.FromStrCurrency('JPY')],
      'trade_product': trade_product,
      'pos_bias': pos_bias,
      'calculator': {
          'symbol_nick': 'bitflyer-fxbtcjpy',
          'ref_symbol_nicks': [],
          'linear_model': model,
          'date': now_dt,
          'sampler_offsets': sampler_offsets,
          'lt_model_param': {
              'version': 1
          },
          'debug': dry_run,
      },
      'executor': {
          'product': trade_product,
          'pos_bias': pos_bias,
          'lot_size': lot_size,
          'stickiness': 0.,
          'min_pos': -lot_size * stack,
          'max_pos': lot_size * stack,
          'order_update_period': 8.,
      },
      'logger': {
          'strategy_name': strategy_name,
          'strategy_group': 'pass_unhedge.bitflyer-fxbtcjpy',
          'trader': 'jaewon'
      }
  }

  strategy_params['logger']['strategy_name'] = strategy_params['logger']['strategy_name'].replace(
      '.', '_')
  strategy_params['logger']['strategy_group'] = strategy_params['logger']['strategy_group'].replace(
      '.', '_')

  # Trading environment config
  if init_og_immediately:
    init_og_after_min = 0
  else:
    init_og_after_min = 20

  exit_after_min = init_og_after_min + 30
  env_config = {
      'og_config_name': og_config,
      'init_order_gateway_after': datetime.timedelta(minutes=init_og_after_min),
      'exit_after': datetime.timedelta(minutes=exit_after_min)
  }

  return strategy_params, env_config


def main(argv):
  FLAGS = flags.FLAGS
  strategy_params, env_config = get_param_set(argv[1],
                                              dry_run=FLAGS.dry_run,
                                              init_og_immediately=False)

  # Run strategy!
  strategy = PassiveUnhedgeModelStrategy(strategy_params, dry_run=FLAGS.dry_run)
  return run_live(strategy, **env_config)


if __name__ == '__main__':
  logging.basicConfig(
      level='DEBUG',
      format='%(levelname)8s %(asctime)s %(name)s %(filename)s:%(lineno)d] %(message)s')
  logging.getLogger('requests').setLevel(logging.WARNING)
  logging.getLogger('urllib3').setLevel(logging.WARNING)
  logging.getLogger('coin.exchange.base.rest.private_queryer_base').setLevel(logging.WARNING)
  logging.getLogger('coin.exchange.okex_futures.ws.subscriber').setLevel(logging.WARNING)

  flags.DEFINE_bool('dry_run', False, 'Dry run')
  app.run(main)
